class Vue {
    constructor(options) {
        this.$options = options
        // 此处需要判断 data若是返回的函数形式
        this._data = options.data()
        this.initData()
    }

    initData(){
        let data = this._data
        let keys = Object.keys(data)
        // 数据代理
        for(let i=0;i<keys.length;i++) {
            Object.defineProperty(this, keys[i], {
                enumerable: true,
                configurable: true,
                get() {
                    return data[keys[i]]
                },
                set(value) {
                    data[keys[i]] = value
                }
            })
        }

        observe(data)
        this.initWatch()
    }

    initWatch() {
        let watch = this.$options.watch
        if(watch) {
            let keys = Object.keys(watch)
            for(let i=0;i<keys.length;i++){
                new Watcher(this, keys[i], watch[keys[i]])
            }
        }
    }

    $watch(key, cb) {
        new Watcher(this, key , cb)
    }
}

function observe(data) {
    let type = Object.prototype.toString.call(data)
    if(type !== '[object Object]' && type !== '[object Array]') return
    new Observer(data)
}

function defineReactive(obj, key, value) {
    observe(obj[key])
    let dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log('收集依赖')
            dep.depend()
            return value
        },
        set(val) {
            if(val === value) return
            console.log('触发依赖')    
            value = val
            dep.notify()
        }
    })
}

function parsePath(path) {
    const bailRE = /[^\w.$]/
    if(bailRE.test(path)) return
    const segments = path.split('.')
    return function (obj) {
        for(let i=0;i<segments.length;i++){
            if(!obj) return
            obj = obj[segments[i]]
        }
        return obj
    }
}

class Observer {
    constructor(data) {
        this.walk(data)
    }

    walk(data) {
        let keys = Object.keys(data)
        for(let i =0;i<keys.length; i++ ){
            defineReactive(data, keys[i], data[keys[i]])
        }
    }
}

class Dep {
    constructor() {
        this.subs = []
    }

    depend() {
        if(Dep.target) {
            this.subs.push(Dep.target)
        }
    }

    notify() {
        this.subs.forEach(watcher => {
            watcher.run()
        })
    }
}

class Watcher{
    constructor(vm, exp , cb) {
        this.vm = vm
        this.getter = parsePath(exp)
        this.cb = cb
        this.value = this.get()
    }

    get() {
        Dep.target = this
        let value = this.getter.call(this.vm, this.vm)
        Dep.target = undefined
        return value
    }

    run() {
        const oldValue = this.value
        this.value = this.get()
        this.cb.call(this.vm, this.value, oldValue )
    }
}